package com.own.component.elastic.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import co.elastic.clients.elasticsearch._types.mapping.Property;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.IdsQuery;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.TotalHitsRelation;
import co.elastic.clients.util.ObjectBuilder;
import com.own.component.common.base.entity.po.BaseBean;
import com.own.component.common.em.SystemEnum;
import com.own.component.common.model.PageModel;
import com.own.component.elastic.entity.BaseElasticPo;
import com.own.component.elastic.entity.BaseElasticQuery;
import com.own.component.elastic.entity.BaseElasticVo;
import com.own.component.elastic.exception.ElasticException;
import com.own.component.elastic.service.BaseElasticIndexService;
import com.own.component.elastic.service.BaseElasticSearchService;
import com.own.component.elastic.service.config.ElasticSearchConfig;
import com.own.component.elastic.util.ElasticMappingResolveUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * ABaseElasticService
 *
 * @author chenxueli
 * @date 2022-09-03 18:42:00
 */
@Slf4j
@Service
public abstract class AbstractBaseElasticSearchService<
        PO extends BaseBean,
        ELASTIC_PO extends BaseElasticPo<PO>,
        ELASTIC_VO extends BaseElasticVo<ELASTIC_PO>,
        QUERY extends BaseElasticQuery
        > implements
        BaseElasticIndexService,
        BaseElasticSearchService<PO, ELASTIC_PO, ELASTIC_VO, QUERY> {

    private final ElasticSearchConfig<PO, ELASTIC_PO, ELASTIC_VO, QUERY> NULL_CONFIG = ElasticSearchConfig.<PO, ELASTIC_PO, ELASTIC_VO, QUERY>builder().build();

    @Resource
    private ElasticsearchClient elasticsearchClient;

    private Class<ELASTIC_PO> elasticPoClass = null;
    /**
     * vo对象的构造方法
     */
    private Constructor<?> elasticVoConstructor = null;
    /**
     * 默认的命中对象处理器
     */
    private final Function<Hit<ELASTIC_PO>, ELASTIC_VO> DEFAULT_RESULT_HIT_HANDLER = item -> {
        var vo = ofVo();
        vo.of(item);
        return vo;
    };
    /**
     * 默认的命中对象处理器
     */
    private final Function<ELASTIC_PO, ELASTIC_VO> DEFAULT_RESULT_HANDLER = item -> {
        var vo = ofVo();
        vo.of(item);
        return vo;
    };

    @SuppressWarnings("all")
    public AbstractBaseElasticSearchService() {
        var array = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments();
        // 必须是六个参数才能使用
        if (array.length == 4) {
            elasticPoClass = (Class<ELASTIC_PO>) array[1];
            var elasticVoClass = (Class<ELASTIC_VO>) array[2];
            // 获取空参构造
            try {
                elasticVoConstructor = elasticVoClass.getConstructor();
            } catch (Exception e) {
                log.error("{}无参构造方法获取失败", elasticVoClass.getName());
            }
        }
    }

    /**
     * 获取对象类型
     *
     * @return 对象类型
     */
    protected Class<ELASTIC_PO> getElasticPoClass() {
        return elasticPoClass;
    }

    /**
     * 根据po对象生成vo对象
     *
     * @return vo对象
     */
    @SuppressWarnings("all")
    protected ELASTIC_VO ofVo() {
        if (elasticVoConstructor == null) {
            throw new ElasticException(SystemEnum.VO_CONSTRUCTOR_ERROR);
        }
        try {
            return (ELASTIC_VO) elasticVoConstructor.newInstance();
        } catch (Exception e) {
            throw new ElasticException(SystemEnum.VO_CONSTRUCTOR_ERROR);
        }
    }

    /**
     * 根据id获取信息
     *
     * @param id 主键id
     * @return 对象信息
     */
    @Override
    public ELASTIC_PO getPoById(String id) {
        try {
            var response = elasticsearchClient.get(document -> document.index(index()).id(id), elasticPoClass);
            return response.source();
        } catch (IOException e) {
            log.error("查询数据失败", e);
            return null;
        }
    }

    /**
     * 根据id获取信息
     *
     * @param id 主键id
     * @return 对象信息
     */
    @Override
    public ELASTIC_VO getById(String id) {
        try {
            var response = elasticsearchClient.get(document -> document.index(index()).id(id), elasticPoClass);
            return DEFAULT_RESULT_HANDLER.apply(response.source());
        } catch (IOException e) {
            log.error("查询数据失败", e);
            return null;
        }
    }

    /**
     * 根据id获取信息
     *
     * @param idList 主键ids
     * @return 对象信息列表
     */
    @Override
    public List<ELASTIC_PO> listPoByIdList(Collection<String> idList) {
        try {
            var response = elasticsearchClient
                    .search(builder -> builder.index(index()).query(IdsQuery.of(ids -> ids.values(idList.stream().toList()))._toQuery()), elasticPoClass);
            return response.hits().hits().stream().map(Hit::source).toList();
        } catch (IOException e) {
            log.error("查询数据失败", e);
            return new ArrayList<>();
        }
    }

    /**
     * 根据id获取信息
     *
     * @param idList 主键ids
     * @return 对象信息列表
     */
    @Override
    public List<ELASTIC_VO> listByIdList(Collection<String> idList) {
        return listByIdList(idList, DEFAULT_RESULT_HIT_HANDLER);
    }

    /**
     * 根据id获取信息
     *
     * @param idList   主键ids
     * @param function 转换函数
     * @return 对象信息列表
     */
    @Override
    public List<ELASTIC_VO> listByIdList(Collection<String> idList, Function<Hit<ELASTIC_PO>, ELASTIC_VO> function) {
        try {
            var response = elasticsearchClient
                    .search(builder -> builder.index(index()).query(IdsQuery.of(ids -> ids.values(idList.stream().toList()))._toQuery()), elasticPoClass);
            return response.hits().hits().stream().map(function).toList();
        } catch (IOException e) {
            log.error("查询数据失败", e);
            return new ArrayList<>();
        }
    }

    /**
     * 分页搜索
     *
     * @param query          查询条件
     * @param config         查询配置信息
     * @param filterConsumer 自定义的筛选条件内容
     * @return 分页结果
     */
    @Override
    public PageModel<ELASTIC_VO> page(QUERY query, ElasticSearchConfig<PO, ELASTIC_PO, ELASTIC_VO, QUERY> config, Consumer<BoolQuery.Builder> filterConsumer) {
        // 获取数据信息
        SearchResponse<ELASTIC_PO> response;
        try {
            response = elasticsearchClient.search(builder -> config.search(index(), query, filterConsumer, builder), elasticPoClass);
        } catch (Exception e) {
            log.error("查询数据失败", e);
            return new PageModel<>(query);
        }
        // 响应数据行数
        var total = response.hits().total();
        var isExactResult = Objects.requireNonNull(total).relation() == TotalHitsRelation.Eq;
        // 超出查询限制的提醒
        if (!isExactResult) {
            log.debug("There are more than " + total.value() + " results");
        }
        // 数据后续操作
        var hits = response.hits().hits();
        var hitHandler = config.getResultHitHandler();
        var list = hitHandler != null ? hits.stream().map(hitHandler).toList() : hits.stream().map(DEFAULT_RESULT_HIT_HANDLER).toList();
        // 数据返回
        return new PageModel<>(list, query, (int) total.value());
    }

    /**
     * 聚合查询
     *
     * @param query          查询条件
     * @param config         查询配置信息
     * @param filterConsumer 自定义的筛选条件内容
     * @param aggregation    聚合信息
     * @return 分页结果
     */
    @Override
    public Aggregate aggregation(QUERY query, ElasticSearchConfig<PO, ELASTIC_PO, ELASTIC_VO, QUERY> config, Consumer<BoolQuery.Builder> filterConsumer, Function<Aggregation.Builder, ObjectBuilder<Aggregation>> aggregation) {
        // 获取数据信息
        SearchResponse<ELASTIC_PO> response;
        try {
            response = elasticsearchClient.search(builder -> config.aggregation(index(), query, filterConsumer, builder).aggregations("test", aggregation), elasticPoClass);
        } catch (Exception e) {
            log.error("查询数据失败", e);
            return null;
        }
        // 响应数据行数
        return response.aggregations().get("test");
    }

    /**
     * 聚合查询
     *
     * @param query          查询条件
     * @param config         查询配置信息
     * @param filterConsumer 自定义的筛选条件内容
     * @param aggregationMap 聚合信息
     * @return 分页结果
     */
    @Override
    public Map<String, Aggregate> aggregation(QUERY query, ElasticSearchConfig<PO, ELASTIC_PO, ELASTIC_VO, QUERY> config, Consumer<BoolQuery.Builder> filterConsumer, Map<String, Aggregation> aggregationMap) {
        // 获取数据信息
        SearchResponse<ELASTIC_PO> response;
        try {
            response = elasticsearchClient.search(
                    builder -> config.aggregation(index(), query, filterConsumer, builder).aggregations(aggregationMap),
                    elasticPoClass
            );
        } catch (Exception e) {
            log.error("查询数据失败", e);
            return new HashMap<>();
        }
        // 响应数据行数
        return response.aggregations();
    }

    /**
     * 查询数量信息
     * 参数构造参考https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/current/searching.html
     *
     * @param query          查询条件
     * @param config         查询配置信息
     * @param filterConsumer 自定义的筛选条件内容
     * @return 数量信息
     */
    @Override
    public long count(QUERY query, ElasticSearchConfig<PO, ELASTIC_PO, ELASTIC_VO, QUERY> config, Consumer<BoolQuery.Builder> filterConsumer) {
        try {

            var response = elasticsearchClient.count(builder -> config.count(index(), query, filterConsumer, builder));
            // 数据返回
            return response.count();
        } catch (Exception e) {
            log.error("查询数据失败", e);
            return 0L;
        }
    }

    /**
     * 文档名称
     *
     * @return 文档名称
     */
    @Override
    public String index() {
        return "";
    }

    /**
     * 获取空的构造信息
     *
     * @return 空的构造信息
     */
    @Override
    public ElasticSearchConfig<PO, ELASTIC_PO, ELASTIC_VO, QUERY> getEmptyConfig() {
        return NULL_CONFIG;
    }

    /**
     * 获取索引映射
     *
     * @return 索引映射
     */
    @Override
    public Map<String, Property> mapping() {
        return ElasticMappingResolveUtil.resolve(elasticPoClass);
    }

}
